<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2021 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
declare (strict_types=1);

namespace app\driver;

use think\App;
use think\contract\LogHandlerInterface;

/**
 * 本地化调试输出到文件
 * 在config/log中配置后在使用
 * 和 think\log\driver\File 对比
 * 1、修改了 __construct 的路径
 * 2、修改 write()方法：
 *   2.1、如果是host则空行（新的一次请求）
 *   2.2、增加保存请求的url
 * 3、修改 save()方法：
 *   3.1、独立日志增加请求的url
 *   3.2、独立日志取消 continue; 总日志也保存地理日志的东西
 * 4、增加当前请求的url方法，避免重复获取
 */
class LogFile implements LogHandlerInterface
{
	
	/**
	 * 必须实现的操作方法
	 * public function save(array $log): bool;
	 */
	
	// 请求的信息
	protected static $requestStr = '';
	protected static $logType    = [];
	
	/**
	 * 配置参数
	 * @var array
	 */
	protected $config = [
		'time_format'  => 'c',
		'single'       => false,
		'file_size'    => 2097152,
		'path'         => '',
		'apart_level'  => [],
		'max_files'    => 0,
		'json'         => false,
		'json_options' => JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES,
		'format'       => '[%s][%s] %s',
	];
	
	// 实例化并传入参数
	public function __construct(App $app, $config = [])
	{
		if(is_array($config)){
			$this->config = array_merge($this->config, $config);
		}
		
		if(empty($this->config['format'])){
			$this->config['format'] = '[%s][%s] %s';
		}
		
		//$site   = $app->request->param('site', 'app');
		$module = $app->request->param('module', 'frontend');
		if(empty($this->config['path'])){
			$this->config['path'] = $app->getRuntimePath().'log';
		}
		if(substr($this->config['path'], -1) != DIRECTORY_SEPARATOR){
			$this->config['path'] .= DIRECTORY_SEPARATOR;
		}
		
		// 日志 指定模块
		//$this->config['path'] .= $site.DIRECTORY_SEPARATOR.$module . DIRECTORY_SEPARATOR;
		$this->config['path'] .= $module.DIRECTORY_SEPARATOR;
	}
	
	// 设置路径
	
	/**
	 * 日志写入接口
	 * @access public
	 * @param array $log 日志信息
	 * @return bool
	 */
	public function save(array $log):bool
	{
		$destination = $this->getMasterLogFile();
		
		$path = dirname($destination);
		!is_dir($path) && mkdir($path, 0755, true);
		
		$info = [];
		
		// 日志信息封装
		$time = \DateTime::createFromFormat('0.u00 U', microtime())->setTimezone(new \DateTimeZone(date_default_timezone_get()))->format($this->config['time_format']);
		
		foreach($log as $type => $val){
			$message = [];
			foreach($val as $msg){
				if(!is_string($msg)){
					$msg = var_export($msg, true);
				}
				
				$message[] = $this->config['json'] ?
					json_encode(['time' => $time, 'type' => $type, 'msg' => $msg], $this->config['json_options']) :
					sprintf($this->config['format'], $time, $type, $msg);
			}
			
			if(true === $this->config['apart_level'] || in_array($type, $this->config['apart_level'])){
				// 独立记录的日志级别
				$filename = $this->getApartLevelFile($path, $type);
				$this->write($message, $filename, $type);
				
				// TODO 如果独立记录某些日志，总日志就不记录了
				//   如果总日志还需要记录，则注释下面的 continue
				// continue;
				
				// 如果独立日志 是 view:展示模板 则不在记录在总日志中了
				//   view:展示模板的日志太长，如果有独立日志，在不用在总日志中记录，避免浪费空间，不方便查看日志
				if($type == 'view'){
					continue;
				}
			}
			
			// 如果是 mysql 的慢日志 则不记录
			//   慢日志也是sql日志中的一部分，不在记录，不然重复
			if($type != 'slow'){
				$info[$type] = $message;
			}
		}
		
		if($info){
			return $this->write($info, $destination);
		}
		
		return true;
	}
	
	// 获取当前url
	protected function getRequestStr()
	{
		if(empty(self::$requestStr)){
			/*if(self::$requestStr){
				$this->url = self::$requestStr;
			}else{
			
			}*/
			self::$requestStr = chr(10).request()->url(true);
		}
		
		return PHP_EOL.self::$requestStr;
	}
	
	/**
	 * 日志写入
	 * @access protected
	 * @param array $message 日志信息
	 * @param string $destination 日志文件
	 * @param bool $addHost 独立日志，增加请求url地址
	 * @return bool
	 */
	protected function write(array $message, string $destination, $from = ''):bool
	{
		// 检测日志文件大小，超过配置大小则备份日志文件重新生成
		$this->checkLogSize($destination);
		
		$info = [];
		
		foreach($message as $type => $msg){
			// 增加空行
			$pre = '';
			$str = is_array($msg) ? implode(PHP_EOL, $msg) : $msg;
			if(($type == 'HOST') && is_string($type)){
				$pre = PHP_EOL;
				if(empty(self::$requestStr)){
					self::$requestStr = $str;
				}
			}
			$info[$type] = $pre.$str;
		}
		
		
		//  独立日志中 增加请求的url
		//[2024-06-03T22:27:13+08:00][HOST] 127.0.0.1 GET http://www.light.com/mtag/123
		// $msg = ($addHost ? self::$requestStr.PHP_EOL.'ddd  ' : '').implode(PHP_EOL, $info).PHP_EOL;
		// $msg = ($from ? $this->getUrl().PHP_EOL : '').implode(PHP_EOL, $info).PHP_EOL;
		
		// 只有第一次才添加
		$msg = implode(PHP_EOL, $info).PHP_EOL;
		if($from){
			if(empty(self::$logType[$from])){
				$msg = $this->getRequestStr().PHP_EOL.$msg;
				// 重新定义
				self::$logType[$from] = true;
			}
		}
		
		return error_log($msg, 3, $destination);
	}
	
	/**
	 * 获取主日志文件名
	 * @access public
	 * @return string
	 */
	protected function getMasterLogFile():string
	{
		
		if($this->config['max_files']){
			$files = glob($this->config['path'].'*.log');
			
			try{
				if(count($files) > $this->config['max_files']){
					unlink($files[0]);
				}
			} catch(\Exception $e){
				//
			}
		}
		
		if($this->config['single']){
			$name     = is_string($this->config['single']) ? $this->config['single'] : 'single';
			$filename = $name.'.log';
		} else{
			if($this->config['max_files']){
				$filename = date('Ymd').'.log';
			} else{
				$filename = date('Ym').DIRECTORY_SEPARATOR.date('d').'.log';
			}
		}
		
		$destination = $this->config['path'].$filename;
		
		return $destination;
	}
	
	/**
	 * 获取独立日志文件名
	 * @access public
	 * @param string $path 日志目录
	 * @param string $type 日志类型
	 * @return string
	 */
	protected function getApartLevelFile(string $path, string $type):string
	{
		
		if($this->config['single']){
			$name = is_string($this->config['single']) ? $this->config['single'] : 'single';
			
			$name .= '_'.$type;
		} elseif($this->config['max_files']){
			$name = date('Ymd').'_'.$type;
		} else{
			$name = date('d').'_'.$type;
		}
		
		return $path.DIRECTORY_SEPARATOR.$name.'.log';
	}
	
	/**
	 * 检查日志文件大小并自动生成备份文件
	 * @access protected
	 * @param string $destination 日志文件
	 * @return void
	 */
	protected function checkLogSize(string $destination):void
	{
		if(is_file($destination) && floor($this->config['file_size']) <= filesize($destination)){
			try{
				rename($destination, dirname($destination).DIRECTORY_SEPARATOR.time().'-'.basename($destination));
			} catch(\Exception $e){
				//
			}
		}
	}
}
